package com.github.eclipseace.mybatis.entity.mapping.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.reflection.invoker.MethodInvoker;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

import com.github.eclipseace.mybatis.entity.mapping.EntityResolver;
import com.github.eclipseace.mybatis.entity.mapping.MappedEntity;
import com.github.eclipseace.mybatis.entity.mapping.PropertyMapping;
import com.github.eclipseace.mybatis.util.Assert;
import com.github.eclipseace.mybatis.util.ReflectionUtils;
import com.github.eclipseace.mybatis.util.StringUtils;

/**
 * @author eclipseAce
 */
public class AnnotationEntityResolver implements EntityResolver {
	protected <A extends Annotation> A findPropertyAnnotation(Class<?> beanType, MetaClass beanMetaType, String name,
			Class<A> annotationType) {
		A annotation = null;
		Field field = ReflectionUtils.getField(beanType, name);
		if (field != null) {
			annotation = field.getAnnotation(annotationType);
		}
		if (annotation == null) {
			Invoker invoker = beanMetaType.getGetInvoker(name);
			if (invoker instanceof MethodInvoker) {
				Method method = (Method) ReflectionUtils.readField(invoker, "method");
				if (method != null) {
					annotation = method.getAnnotation(annotationType);
				}
			}
		}
		return annotation;
	}

	@Override
	public MappedEntity resolve(Configuration configuration, Class<?> beanType) {
		MetaClass beanMetaType = MetaClass.forClass(beanType, configuration.getReflectorFactory());

		PropertyMapping idProperty = null;
		List<PropertyMapping> properties = new ArrayList<>();
		for (String propertyName : beanMetaType.getGetterNames()) {
			Class<?> propertyType = beanMetaType.getGetterType(propertyName);
			Transient aTransient = findPropertyAnnotation(beanType, beanMetaType, propertyName, Transient.class);
			if (aTransient != null) {
				continue;
			}
			Column aColumn = findPropertyAnnotation(beanType, beanMetaType, propertyName, Column.class);
			Id aId = findPropertyAnnotation(beanType, beanMetaType, propertyName, Id.class);

			String column = propertyName;
			if (aColumn != null && !aColumn.name().isEmpty()) {
				column = aColumn.name();
			} else if (configuration.isMapUnderscoreToCamelCase()) {
				column = StringUtils.convertCamelCaseToUnderscore(propertyName);
			}

			JdbcType jdbcType = JdbcType.UNDEFINED;
			// if (aColumn != null) {
			// jdbcType = aColumn.jdbcType();
			// }

			boolean insertable = true;
			boolean updatable = true;
			if (aColumn != null) {
				insertable = aColumn.insertable();
				updatable = aColumn.updatable();
			}

			PropertyMapping property = new PropertyMapping(propertyName, propertyType, column, jdbcType, insertable,
					updatable);
			if (aId != null) {
				Assert.isNull(idProperty, "multiple id properties found");
				idProperty = property;
			} else {
				properties.add(property);
			}
		}
		Assert.isNotNull(idProperty, "no id property found");

		Table aTable = beanType.getAnnotation(Table.class);
		String table = beanType.getSimpleName();
		String catalog = null;
		String schema = null;
		if (aTable != null) {
			if (!aTable.name().isEmpty()) {
				table = aTable.name();
			}
			if (!aTable.catalog().isEmpty()) {
				catalog = aTable.catalog();
			}
			if (!aTable.schema().isEmpty()) {
				schema = aTable.schema();
			}
		}

		return new MappedEntity(beanType, table, catalog, schema, idProperty, properties);
	}
}
